{
  "cells": [
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# `unlinkables_chart`\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "metadata": {
        "tags": [
          "hide_input"
        ]
      },
      "outputs": [
        {
          "data": {
            "text/html": [
              "\n",
              "<style>\n",
              "  #altair-viz-445782ef0d9f40bb986943f21b33f0f7.vega-embed {\n",
              "    width: 100%;\n",
              "    display: flex;\n",
              "  }\n",
              "\n",
              "  #altair-viz-445782ef0d9f40bb986943f21b33f0f7.vega-embed details,\n",
              "  #altair-viz-445782ef0d9f40bb986943f21b33f0f7.vega-embed details summary {\n",
              "    position: relative;\n",
              "  }\n",
              "</style>\n",
              "<div id=\"altair-viz-445782ef0d9f40bb986943f21b33f0f7\"></div>\n",
              "<script type=\"text/javascript\">\n",
              "  var VEGA_DEBUG = (typeof VEGA_DEBUG == \"undefined\") ? {} : VEGA_DEBUG;\n",
              "  (function(spec, embedOpt){\n",
              "    let outputDiv = document.currentScript.previousElementSibling;\n",
              "    if (outputDiv.id !== \"altair-viz-445782ef0d9f40bb986943f21b33f0f7\") {\n",
              "      outputDiv = document.getElementById(\"altair-viz-445782ef0d9f40bb986943f21b33f0f7\");\n",
              "    }\n",
              "    const paths = {\n",
              "      \"vega\": \"https://cdn.jsdelivr.net/npm/vega@5?noext\",\n",
              "      \"vega-lib\": \"https://cdn.jsdelivr.net/npm/vega-lib?noext\",\n",
              "      \"vega-lite\": \"https://cdn.jsdelivr.net/npm/vega-lite@5.17.0?noext\",\n",
              "      \"vega-embed\": \"https://cdn.jsdelivr.net/npm/vega-embed@6?noext\",\n",
              "    };\n",
              "\n",
              "    function maybeLoadScript(lib, version) {\n",
              "      var key = `${lib.replace(\"-\", \"\")}_version`;\n",
              "      return (VEGA_DEBUG[key] == version) ?\n",
              "        Promise.resolve(paths[lib]) :\n",
              "        new Promise(function(resolve, reject) {\n",
              "          var s = document.createElement('script');\n",
              "          document.getElementsByTagName(\"head\")[0].appendChild(s);\n",
              "          s.async = true;\n",
              "          s.onload = () => {\n",
              "            VEGA_DEBUG[key] = version;\n",
              "            return resolve(paths[lib]);\n",
              "          };\n",
              "          s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n",
              "          s.src = paths[lib];\n",
              "        });\n",
              "    }\n",
              "\n",
              "    function showError(err) {\n",
              "      outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n",
              "      throw err;\n",
              "    }\n",
              "\n",
              "    function displayChart(vegaEmbed) {\n",
              "      vegaEmbed(outputDiv, spec, embedOpt)\n",
              "        .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n",
              "    }\n",
              "\n",
              "    if(typeof define === \"function\" && define.amd) {\n",
              "      requirejs.config({paths});\n",
              "      require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n",
              "    } else {\n",
              "      maybeLoadScript(\"vega\", \"5\")\n",
              "        .then(() => maybeLoadScript(\"vega-lite\", \"5.17.0\"))\n",
              "        .then(() => maybeLoadScript(\"vega-embed\", \"6\"))\n",
              "        .catch(showError)\n",
              "        .then(() => displayChart(vegaEmbed));\n",
              "    }\n",
              "  })({\"config\": {\"view\": {\"continuousWidth\": 400, \"continuousHeight\": 300}}, \"layer\": [{\"mark\": {\"type\": \"line\"}, \"encoding\": {\"x\": {\"axis\": {\"format\": \"+\", \"title\": \"Threshold match weight\"}, \"field\": \"match_weight\", \"type\": \"quantitative\"}, \"y\": {\"axis\": {\"format\": \"%\", \"title\": \"Percentage of unlinkable records\"}, \"field\": \"cum_prop\", \"type\": \"quantitative\"}}}, {\"mark\": {\"type\": \"point\"}, \"encoding\": {\"opacity\": {\"condition\": {\"param\": \"x_match_weight_y_cum_prop_coords_of_mouse\", \"value\": 1, \"empty\": false}, \"value\": 0}, \"tooltip\": [{\"field\": \"match_weight\", \"format\": \"+.5\", \"title\": \"Match weight\", \"type\": \"quantitative\"}, {\"field\": \"match_probability\", \"format\": \".5\", \"title\": \"Match probability\", \"type\": \"quantitative\"}, {\"field\": \"cum_prop\", \"format\": \".3%\", \"title\": \"Proportion of unlinkable records\", \"type\": \"quantitative\"}], \"x\": {\"axis\": {\"title\": \"Threshold match weight\"}, \"field\": \"match_weight\", \"type\": \"quantitative\"}, \"y\": {\"axis\": {\"format\": \"%\", \"title\": \"Percentage of unlinkable records\"}, \"field\": \"cum_prop\", \"type\": \"quantitative\"}}, \"name\": \"mouse_coords\"}, {\"mark\": {\"type\": \"rule\", \"color\": \"gray\"}, \"encoding\": {\"x\": {\"field\": \"match_weight\", \"type\": \"quantitative\"}}, \"transform\": [{\"filter\": {\"param\": \"x_match_weight_y_cum_prop_coords_of_mouse\", \"empty\": false}}]}, {\"mark\": {\"type\": \"rule\", \"color\": \"gray\"}, \"encoding\": {\"y\": {\"field\": \"cum_prop\", \"type\": \"quantitative\"}}, \"transform\": [{\"filter\": {\"param\": \"x_match_weight_y_cum_prop_coords_of_mouse\", \"empty\": false}}]}], \"data\": {\"name\": \"data-426ca3b00a98301d37b92ebdd0bfa67c\"}, \"height\": 400, \"params\": [{\"name\": \"x_match_weight_y_cum_prop_coords_of_mouse\", \"select\": {\"type\": \"point\", \"fields\": [\"match_weight\", \"cum_prop\"], \"nearest\": true, \"on\": \"mouseover\"}, \"views\": [\"mouse_coords\"]}], \"title\": {\"text\": \"Unlinkable records\", \"subtitle\": \"Records with insufficient information to exceed a given match threshold\"}, \"width\": 400, \"$schema\": \"https://vega.github.io/schema/vega-lite/v5.9.3.json\", \"datasets\": {\"data-426ca3b00a98301d37b92ebdd0bfa67c\": [{\"match_weight\": -5.49, \"match_probability\": 0.02183, \"prop\": 0.0020000000949949026, \"cum_prop\": 0.0020000000949949026}, {\"match_weight\": -0.46, \"match_probability\": 0.42098, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.003000000142492354}, {\"match_weight\": 0.92, \"match_probability\": 0.65413, \"prop\": 0.003000000026077032, \"cum_prop\": 0.006000000168569386}, {\"match_weight\": 0.99, \"match_probability\": 0.66461, \"prop\": 0.007000000216066837, \"cum_prop\": 0.013000000384636223}, {\"match_weight\": 1.03, \"match_probability\": 0.67059, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.014000000432133675}, {\"match_weight\": 2.32, \"match_probability\": 0.83296, \"prop\": 0.004999999888241291, \"cum_prop\": 0.019000000320374966}, {\"match_weight\": 2.39, \"match_probability\": 0.83935, \"prop\": 0.0020000000949949026, \"cum_prop\": 0.02100000041536987}, {\"match_weight\": 2.49, \"match_probability\": 0.84888, \"prop\": 0.006000000052154064, \"cum_prop\": 0.027000000467523932}, {\"match_weight\": 3.35, \"match_probability\": 0.91054, \"prop\": 0.003000000026077032, \"cum_prop\": 0.030000000493600965}, {\"match_weight\": 3.89, \"match_probability\": 0.93675, \"prop\": 0.00800000037997961, \"cum_prop\": 0.038000000873580575}, {\"match_weight\": 4.5, \"match_probability\": 0.95762, \"prop\": 0.003000000026077032, \"cum_prop\": 0.04100000089965761}, {\"match_weight\": 4.71, \"match_probability\": 0.96316, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.04200000094715506}, {\"match_weight\": 5.43, \"match_probability\": 0.97729, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.04300000099465251}, {\"match_weight\": 5.58, \"match_probability\": 0.97956, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.04400000104214996}, {\"match_weight\": 5.67, \"match_probability\": 0.98067, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.04500000108964741}, {\"match_weight\": 5.73, \"match_probability\": 0.98154, \"prop\": 0.0020000000949949026, \"cum_prop\": 0.047000001184642315}, {\"match_weight\": 5.75, \"match_probability\": 0.98179, \"prop\": 0.0020000000949949026, \"cum_prop\": 0.04900000127963722}, {\"match_weight\": 5.82, \"match_probability\": 0.98261, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.05000000132713467}, {\"match_weight\": 5.95, \"match_probability\": 0.98403, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.05100000137463212}, {\"match_weight\": 6.0, \"match_probability\": 0.98463, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.05200000142212957}, {\"match_weight\": 6.12, \"match_probability\": 0.98582, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.05300000146962702}, {\"match_weight\": 6.17, \"match_probability\": 0.98628, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.054000001517124474}, {\"match_weight\": 6.58, \"match_probability\": 0.98968, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.055000001564621925}, {\"match_weight\": 6.65, \"match_probability\": 0.99014, \"prop\": 0.003000000026077032, \"cum_prop\": 0.05800000159069896}, {\"match_weight\": 6.82, \"match_probability\": 0.99123, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.05900000163819641}, {\"match_weight\": 6.93, \"match_probability\": 0.99187, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.06000000168569386}, {\"match_weight\": 6.95, \"match_probability\": 0.99195, \"prop\": 0.003000000026077032, \"cum_prop\": 0.06300000171177089}, {\"match_weight\": 7.01, \"match_probability\": 0.99231, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.06400000175926834}, {\"match_weight\": 7.15, \"match_probability\": 0.99302, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.0650000018067658}, {\"match_weight\": 7.24, \"match_probability\": 0.99341, \"prop\": 0.003000000026077032, \"cum_prop\": 0.06800000183284283}, {\"match_weight\": 7.32, \"match_probability\": 0.99379, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.06900000188034028}, {\"match_weight\": 7.39, \"match_probability\": 0.99408, \"prop\": 0.023000000044703484, \"cum_prop\": 0.09200000192504376}, {\"match_weight\": 7.5, \"match_probability\": 0.9945, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.09300000197254121}, {\"match_weight\": 7.52, \"match_probability\": 0.99457, \"prop\": 0.0020000000949949026, \"cum_prop\": 0.09500000206753612}, {\"match_weight\": 7.62, \"match_probability\": 0.99495, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.09600000211503357}, {\"match_weight\": 7.82, \"match_probability\": 0.99559, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.09700000216253102}, {\"match_weight\": 8.32, \"match_probability\": 0.99689, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.09800000221002847}, {\"match_weight\": 8.79, \"match_probability\": 0.99775, \"prop\": 0.020999999716877937, \"cum_prop\": 0.1190000019269064}, {\"match_weight\": 8.82, \"match_probability\": 0.99779, \"prop\": 0.0020000000949949026, \"cum_prop\": 0.12100000202190131}, {\"match_weight\": 8.9, \"match_probability\": 0.9979, \"prop\": 0.01899999938905239, \"cum_prop\": 0.1400000014109537}, {\"match_weight\": 8.96, \"match_probability\": 0.998, \"prop\": 0.014999999664723873, \"cum_prop\": 0.15500000107567757}, {\"match_weight\": 9.32, \"match_probability\": 0.99844, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.15600000112317502}, {\"match_weight\": 9.75, \"match_probability\": 0.99884, \"prop\": 0.006000000052154064, \"cum_prop\": 0.1620000011753291}, {\"match_weight\": 9.82, \"match_probability\": 0.99889, \"prop\": 0.006000000052154064, \"cum_prop\": 0.16800000122748315}, {\"match_weight\": 10.29, \"match_probability\": 0.9992, \"prop\": 0.01899999938905239, \"cum_prop\": 0.18700000061653554}, {\"match_weight\": 10.32, \"match_probability\": 0.99922, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.188000000664033}, {\"match_weight\": 10.36, \"match_probability\": 0.99924, \"prop\": 0.019999999552965164, \"cum_prop\": 0.20800000021699816}, {\"match_weight\": 10.9, \"match_probability\": 0.99948, \"prop\": 0.00800000037997961, \"cum_prop\": 0.21600000059697777}, {\"match_weight\": 11.18, \"match_probability\": 0.99957, \"prop\": 0.004000000189989805, \"cum_prop\": 0.22000000078696758}, {\"match_weight\": 11.32, \"match_probability\": 0.99961, \"prop\": 0.003000000026077032, \"cum_prop\": 0.2230000008130446}, {\"match_weight\": 11.83, \"match_probability\": 0.99973, \"prop\": 0.003000000026077032, \"cum_prop\": 0.22600000083912164}, {\"match_weight\": 12.06, \"match_probability\": 0.99977, \"prop\": 0.0020000000949949026, \"cum_prop\": 0.22800000093411654}, {\"match_weight\": 12.23, \"match_probability\": 0.99979, \"prop\": 0.003000000026077032, \"cum_prop\": 0.23100000096019357}, {\"match_weight\": 12.47, \"match_probability\": 0.99982, \"prop\": 0.014999999664723873, \"cum_prop\": 0.24600000062491745}, {\"match_weight\": 12.52, \"match_probability\": 0.99983, \"prop\": 0.006000000052154064, \"cum_prop\": 0.2520000006770715}, {\"match_weight\": 12.64, \"match_probability\": 0.99984, \"prop\": 0.004000000189989805, \"cum_prop\": 0.2560000008670613}, {\"match_weight\": 12.75, \"match_probability\": 0.99985, \"prop\": 0.003000000026077032, \"cum_prop\": 0.25900000089313835}, {\"match_weight\": 12.9, \"match_probability\": 0.99987, \"prop\": 0.004000000189989805, \"cum_prop\": 0.26300000108312815}, {\"match_weight\": 13.34, \"match_probability\": 0.9999, \"prop\": 0.007000000216066837, \"cum_prop\": 0.270000001299195}, {\"match_weight\": 13.42, \"match_probability\": 0.99991, \"prop\": 0.008999999612569809, \"cum_prop\": 0.2790000009117648}, {\"match_weight\": 13.64, \"match_probability\": 0.99992, \"prop\": 0.01600000075995922, \"cum_prop\": 0.295000001671724}, {\"match_weight\": 13.9, \"match_probability\": 0.99993, \"prop\": 0.014999999664723873, \"cum_prop\": 0.3100000013364479}, {\"match_weight\": 14.14, \"match_probability\": 0.99994, \"prop\": 0.024000000208616257, \"cum_prop\": 0.33400000154506415}, {\"match_weight\": 14.41, \"match_probability\": 0.99995, \"prop\": 0.008999999612569809, \"cum_prop\": 0.34300000115763396}, {\"match_weight\": 14.8, \"match_probability\": 0.99996, \"prop\": 0.020999999716877937, \"cum_prop\": 0.3640000008745119}, {\"match_weight\": 15.23, \"match_probability\": 0.99997, \"prop\": 0.01899999938905239, \"cum_prop\": 0.3830000002635643}, {\"match_weight\": 15.73, \"match_probability\": 0.99998, \"prop\": 0.11800000071525574, \"cum_prop\": 0.50100000097882}, {\"match_weight\": 16.8, \"match_probability\": 0.99999, \"prop\": 0.13099999725818634, \"cum_prop\": 0.6319999982370064}]}}, {\"mode\": \"vega-lite\"});\n",
              "</script>"
            ],
            "text/plain": [
              "alt.LayerChart(...)"
            ]
          },
          "execution_count": 2,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "chart"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\n",
        "!!! info \"At a glance\"\n",
        "    **Useful for:** Looking at how many records have insufficient information to be linked to themselves.\n",
        "\n",
        "    **API Documentation:** [unlinkables_chart()](../api_docs/evaluation.md#splink.internals.linker_components.evaluation.LinkerEvalution.unlinkables_chart)\n",
        "\n",
        "    **What is needed to generate the chart?** A trained Splink model"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### What the chart shows\n",
        "\n",
        "The `unlinkables_chart` shows the proportion of records with insufficient information to be matched to themselves at differing match thresholds.\n",
        "\n",
        "??? note \"What the chart tooltip shows\"\n",
        "\n",
        "    ![](./img/unlinkables_chart_tooltip.png)\n",
        "\n",
        "    This tooltip shows a number of statistics based on the match weight of the selected point of the line, including:\n",
        "\n",
        "    - The chosen match weight and corresponding match probability.\n",
        "    - The proportion of records of records that cannot be linked to themselves given the chosen match weight threshold for a match."
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "<hr>"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### How to interpret the chart\n",
        "\n",
        "This chart gives an indication of both data quality and/or model predictiveness within a Splink model. If a high proportion of records are not linkable to themselves at a low match threshold (e.g. 0 match weight/50% probability) we can conclude that either/or:\n",
        "\n",
        "- the data quality is low enough such that a significant proportion of records are unable to be linked to themselves\n",
        "- the parameters of the Splink model are such that features have not been assigned enough weight, and therefore will not perform well\n",
        "\n",
        "This chart also gives an indication of the number of False Negatives (i.e. missed links) at a given threshold, assuming sufficient data quality. For example:\n",
        "\n",
        "- we know that a record should be linked to itself, so seeing that a match weight $\\approx$ 10 gives 16% of records unable to link to themselves\n",
        "- exact matches generally provide the strongest matches, therefore, we can expect that any \"fuzzy\" matches to have lower match scores. As a result, we can deduce that the propoertion of False Negatives will be higher than 16%.\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "<hr>"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Actions to take as a result of the chart\n",
        "\n",
        "If the level of unlinkable records is extremely high at low match weight thresholds, you have a poorly performing model. This may be an issue that can be resolved by tweaking the models comparisons, but if the poor performance is primarily down to poor data quality, there is very little that can be done to improve the model.\n",
        "\n",
        "When interpretted as an indicator of False Negatives, this chart can be used to establish an upper bound for match weight, depending on the propensity for False Negatives in the particular use case."
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Worked Example"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {
        "tags": [
          "hide_output"
        ]
      },
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "You are using the default value for `max_pairs`, which may be too small and thus lead to inaccurate estimates for your model's u-parameters. Consider increasing to 1e8 or 1e9, which will result in more accurate estimates, but with a longer run time.\n",
            "----- Estimating u probabilities using random sampling -----\n",
            "u probability not trained for dob - Abs difference of 'transformed dob <= 1 month' (comparison vector value: 1). This usually means the comparison level was never observed in the training data.\n",
            "\n",
            "Estimated u probabilities using random sampling\n",
            "\n",
            "Your model is not yet fully trained. Missing estimates for:\n",
            "    - first_name (no m values are trained).\n",
            "    - surname (no m values are trained).\n",
            "    - dob (some u values are not trained, no m values are trained).\n",
            "    - city (no m values are trained).\n",
            "    - email (no m values are trained).\n",
            "\n",
            "----- Starting EM training session -----\n",
            "\n",
            "Estimating the m probabilities of the model by blocking on:\n",
            "(l.\"first_name\" = r.\"first_name\") AND (l.\"surname\" = r.\"surname\")\n",
            "\n",
            "Parameter estimates will be made for the following comparison(s):\n",
            "    - dob\n",
            "    - city\n",
            "    - email\n",
            "\n",
            "Parameter estimates cannot be made for the following comparison(s) since they are used in the blocking rules: \n",
            "    - first_name\n",
            "    - surname\n",
            "\n",
            "WARNING:\n",
            "Level Abs difference of 'transformed dob <= 1 month' on comparison dob not observed in dataset, unable to train m value\n",
            "\n",
            "WARNING:\n",
            "Level Jaro-Winkler distance of transformed email >= 0.88 on comparison email not observed in dataset, unable to train m value\n",
            "\n",
            "Iteration 1: Largest change in params was -0.466 in the m_probability of dob, level `Exact match on dob`\n",
            "Iteration 2: Largest change in params was 0.141 in probability_two_random_records_match\n",
            "Iteration 3: Largest change in params was 0.0319 in probability_two_random_records_match\n",
            "Iteration 4: Largest change in params was 0.0105 in probability_two_random_records_match\n",
            "Iteration 5: Largest change in params was 0.00435 in probability_two_random_records_match\n",
            "Iteration 6: Largest change in params was 0.00208 in probability_two_random_records_match\n",
            "Iteration 7: Largest change in params was 0.00109 in probability_two_random_records_match\n",
            "Iteration 8: Largest change in params was 0.000601 in probability_two_random_records_match\n",
            "Iteration 9: Largest change in params was 0.000342 in probability_two_random_records_match\n",
            "Iteration 10: Largest change in params was 0.000197 in probability_two_random_records_match\n",
            "Iteration 11: Largest change in params was 0.000115 in probability_two_random_records_match\n",
            "Iteration 12: Largest change in params was 6.75e-05 in probability_two_random_records_match\n",
            "\n",
            "EM converged after 12 iterations\n",
            "m probability not trained for dob - Abs difference of 'transformed dob <= 1 month' (comparison vector value: 1). This usually means the comparison level was never observed in the training data.\n",
            "m probability not trained for email - Jaro-Winkler distance of transformed email >= 0.88 (comparison vector value: 1). This usually means the comparison level was never observed in the training data.\n",
            "\n",
            "Your model is not yet fully trained. Missing estimates for:\n",
            "    - first_name (no m values are trained).\n",
            "    - surname (no m values are trained).\n",
            "    - dob (some u values are not trained, some m values are not trained).\n",
            "    - email (some m values are not trained).\n",
            "\n",
            "----- Starting EM training session -----\n",
            "\n",
            "Estimating the m probabilities of the model by blocking on:\n",
            "l.\"dob\" = r.\"dob\"\n",
            "\n",
            "Parameter estimates will be made for the following comparison(s):\n",
            "    - first_name\n",
            "    - surname\n",
            "    - city\n",
            "    - email\n",
            "\n",
            "Parameter estimates cannot be made for the following comparison(s) since they are used in the blocking rules: \n",
            "    - dob\n",
            "\n",
            "WARNING:\n",
            "Level Jaro-Winkler distance of transformed email >= 0.88 on comparison email not observed in dataset, unable to train m value\n",
            "\n",
            "Iteration 1: Largest change in params was 0.64 in probability_two_random_records_match\n",
            "Iteration 2: Largest change in params was 0.176 in probability_two_random_records_match\n",
            "Iteration 3: Largest change in params was 0.0846 in the m_probability of first_name, level `All other comparisons`\n",
            "Iteration 4: Largest change in params was 0.0268 in probability_two_random_records_match\n",
            "Iteration 5: Largest change in params was 0.0101 in probability_two_random_records_match\n",
            "Iteration 6: Largest change in params was 0.00431 in probability_two_random_records_match\n",
            "Iteration 7: Largest change in params was 0.00198 in probability_two_random_records_match\n",
            "Iteration 8: Largest change in params was 0.000936 in probability_two_random_records_match\n",
            "Iteration 9: Largest change in params was 0.00045 in probability_two_random_records_match\n",
            "Iteration 10: Largest change in params was 0.000218 in probability_two_random_records_match\n",
            "Iteration 11: Largest change in params was 0.000106 in probability_two_random_records_match\n",
            "Iteration 12: Largest change in params was 5.19e-05 in probability_two_random_records_match\n",
            "\n",
            "EM converged after 12 iterations\n",
            "m probability not trained for email - Jaro-Winkler distance of transformed email >= 0.88 (comparison vector value: 1). This usually means the comparison level was never observed in the training data.\n",
            "\n",
            "Your model is not yet fully trained. Missing estimates for:\n",
            "    - dob (some u values are not trained, some m values are not trained).\n",
            "    - email (some m values are not trained).\n"
          ]
        },
        {
          "data": {
            "text/html": [
              "\n",
              "<style>\n",
              "  #altair-viz-c2162739dd5f413fa524273cdffd1999.vega-embed {\n",
              "    width: 100%;\n",
              "    display: flex;\n",
              "  }\n",
              "\n",
              "  #altair-viz-c2162739dd5f413fa524273cdffd1999.vega-embed details,\n",
              "  #altair-viz-c2162739dd5f413fa524273cdffd1999.vega-embed details summary {\n",
              "    position: relative;\n",
              "  }\n",
              "</style>\n",
              "<div id=\"altair-viz-c2162739dd5f413fa524273cdffd1999\"></div>\n",
              "<script type=\"text/javascript\">\n",
              "  var VEGA_DEBUG = (typeof VEGA_DEBUG == \"undefined\") ? {} : VEGA_DEBUG;\n",
              "  (function(spec, embedOpt){\n",
              "    let outputDiv = document.currentScript.previousElementSibling;\n",
              "    if (outputDiv.id !== \"altair-viz-c2162739dd5f413fa524273cdffd1999\") {\n",
              "      outputDiv = document.getElementById(\"altair-viz-c2162739dd5f413fa524273cdffd1999\");\n",
              "    }\n",
              "    const paths = {\n",
              "      \"vega\": \"https://cdn.jsdelivr.net/npm/vega@5?noext\",\n",
              "      \"vega-lib\": \"https://cdn.jsdelivr.net/npm/vega-lib?noext\",\n",
              "      \"vega-lite\": \"https://cdn.jsdelivr.net/npm/vega-lite@5.17.0?noext\",\n",
              "      \"vega-embed\": \"https://cdn.jsdelivr.net/npm/vega-embed@6?noext\",\n",
              "    };\n",
              "\n",
              "    function maybeLoadScript(lib, version) {\n",
              "      var key = `${lib.replace(\"-\", \"\")}_version`;\n",
              "      return (VEGA_DEBUG[key] == version) ?\n",
              "        Promise.resolve(paths[lib]) :\n",
              "        new Promise(function(resolve, reject) {\n",
              "          var s = document.createElement('script');\n",
              "          document.getElementsByTagName(\"head\")[0].appendChild(s);\n",
              "          s.async = true;\n",
              "          s.onload = () => {\n",
              "            VEGA_DEBUG[key] = version;\n",
              "            return resolve(paths[lib]);\n",
              "          };\n",
              "          s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n",
              "          s.src = paths[lib];\n",
              "        });\n",
              "    }\n",
              "\n",
              "    function showError(err) {\n",
              "      outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n",
              "      throw err;\n",
              "    }\n",
              "\n",
              "    function displayChart(vegaEmbed) {\n",
              "      vegaEmbed(outputDiv, spec, embedOpt)\n",
              "        .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n",
              "    }\n",
              "\n",
              "    if(typeof define === \"function\" && define.amd) {\n",
              "      requirejs.config({paths});\n",
              "      require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n",
              "    } else {\n",
              "      maybeLoadScript(\"vega\", \"5\")\n",
              "        .then(() => maybeLoadScript(\"vega-lite\", \"5.17.0\"))\n",
              "        .then(() => maybeLoadScript(\"vega-embed\", \"6\"))\n",
              "        .catch(showError)\n",
              "        .then(() => displayChart(vegaEmbed));\n",
              "    }\n",
              "  })({\"config\": {\"view\": {\"continuousWidth\": 400, \"continuousHeight\": 300}}, \"layer\": [{\"mark\": {\"type\": \"line\"}, \"encoding\": {\"x\": {\"axis\": {\"format\": \"+\", \"title\": \"Threshold match weight\"}, \"field\": \"match_weight\", \"type\": \"quantitative\"}, \"y\": {\"axis\": {\"format\": \"%\", \"title\": \"Percentage of unlinkable records\"}, \"field\": \"cum_prop\", \"type\": \"quantitative\"}}}, {\"mark\": {\"type\": \"point\"}, \"encoding\": {\"opacity\": {\"condition\": {\"param\": \"x_match_weight_y_cum_prop_coords_of_mouse\", \"value\": 1, \"empty\": false}, \"value\": 0}, \"tooltip\": [{\"field\": \"match_weight\", \"format\": \"+.5\", \"title\": \"Match weight\", \"type\": \"quantitative\"}, {\"field\": \"match_probability\", \"format\": \".5\", \"title\": \"Match probability\", \"type\": \"quantitative\"}, {\"field\": \"cum_prop\", \"format\": \".3%\", \"title\": \"Proportion of unlinkable records\", \"type\": \"quantitative\"}], \"x\": {\"axis\": {\"title\": \"Threshold match weight\"}, \"field\": \"match_weight\", \"type\": \"quantitative\"}, \"y\": {\"axis\": {\"format\": \"%\", \"title\": \"Percentage of unlinkable records\"}, \"field\": \"cum_prop\", \"type\": \"quantitative\"}}, \"name\": \"mouse_coords\"}, {\"mark\": {\"type\": \"rule\", \"color\": \"gray\"}, \"encoding\": {\"x\": {\"field\": \"match_weight\", \"type\": \"quantitative\"}}, \"transform\": [{\"filter\": {\"param\": \"x_match_weight_y_cum_prop_coords_of_mouse\", \"empty\": false}}]}, {\"mark\": {\"type\": \"rule\", \"color\": \"gray\"}, \"encoding\": {\"y\": {\"field\": \"cum_prop\", \"type\": \"quantitative\"}}, \"transform\": [{\"filter\": {\"param\": \"x_match_weight_y_cum_prop_coords_of_mouse\", \"empty\": false}}]}], \"data\": {\"name\": \"data-426ca3b00a98301d37b92ebdd0bfa67c\"}, \"height\": 400, \"params\": [{\"name\": \"x_match_weight_y_cum_prop_coords_of_mouse\", \"select\": {\"type\": \"point\", \"fields\": [\"match_weight\", \"cum_prop\"], \"nearest\": true, \"on\": \"mouseover\"}, \"views\": [\"mouse_coords\"]}], \"title\": {\"text\": \"Unlinkable records\", \"subtitle\": \"Records with insufficient information to exceed a given match threshold\"}, \"width\": 400, \"$schema\": \"https://vega.github.io/schema/vega-lite/v5.9.3.json\", \"datasets\": {\"data-426ca3b00a98301d37b92ebdd0bfa67c\": [{\"match_weight\": -5.49, \"match_probability\": 0.02183, \"prop\": 0.0020000000949949026, \"cum_prop\": 0.0020000000949949026}, {\"match_weight\": -0.46, \"match_probability\": 0.42098, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.003000000142492354}, {\"match_weight\": 0.92, \"match_probability\": 0.65413, \"prop\": 0.003000000026077032, \"cum_prop\": 0.006000000168569386}, {\"match_weight\": 0.99, \"match_probability\": 0.66461, \"prop\": 0.007000000216066837, \"cum_prop\": 0.013000000384636223}, {\"match_weight\": 1.03, \"match_probability\": 0.67059, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.014000000432133675}, {\"match_weight\": 2.32, \"match_probability\": 0.83296, \"prop\": 0.004999999888241291, \"cum_prop\": 0.019000000320374966}, {\"match_weight\": 2.39, \"match_probability\": 0.83935, \"prop\": 0.0020000000949949026, \"cum_prop\": 0.02100000041536987}, {\"match_weight\": 2.49, \"match_probability\": 0.84888, \"prop\": 0.006000000052154064, \"cum_prop\": 0.027000000467523932}, {\"match_weight\": 3.35, \"match_probability\": 0.91054, \"prop\": 0.003000000026077032, \"cum_prop\": 0.030000000493600965}, {\"match_weight\": 3.89, \"match_probability\": 0.93675, \"prop\": 0.00800000037997961, \"cum_prop\": 0.038000000873580575}, {\"match_weight\": 4.5, \"match_probability\": 0.95762, \"prop\": 0.003000000026077032, \"cum_prop\": 0.04100000089965761}, {\"match_weight\": 4.71, \"match_probability\": 0.96316, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.04200000094715506}, {\"match_weight\": 5.43, \"match_probability\": 0.97729, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.04300000099465251}, {\"match_weight\": 5.58, \"match_probability\": 0.97956, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.04400000104214996}, {\"match_weight\": 5.67, \"match_probability\": 0.98067, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.04500000108964741}, {\"match_weight\": 5.73, \"match_probability\": 0.98154, \"prop\": 0.0020000000949949026, \"cum_prop\": 0.047000001184642315}, {\"match_weight\": 5.75, \"match_probability\": 0.98179, \"prop\": 0.0020000000949949026, \"cum_prop\": 0.04900000127963722}, {\"match_weight\": 5.82, \"match_probability\": 0.98261, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.05000000132713467}, {\"match_weight\": 5.95, \"match_probability\": 0.98403, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.05100000137463212}, {\"match_weight\": 6.0, \"match_probability\": 0.98463, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.05200000142212957}, {\"match_weight\": 6.12, \"match_probability\": 0.98582, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.05300000146962702}, {\"match_weight\": 6.17, \"match_probability\": 0.98628, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.054000001517124474}, {\"match_weight\": 6.58, \"match_probability\": 0.98968, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.055000001564621925}, {\"match_weight\": 6.65, \"match_probability\": 0.99014, \"prop\": 0.003000000026077032, \"cum_prop\": 0.05800000159069896}, {\"match_weight\": 6.82, \"match_probability\": 0.99123, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.05900000163819641}, {\"match_weight\": 6.93, \"match_probability\": 0.99187, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.06000000168569386}, {\"match_weight\": 6.95, \"match_probability\": 0.99195, \"prop\": 0.003000000026077032, \"cum_prop\": 0.06300000171177089}, {\"match_weight\": 7.01, \"match_probability\": 0.99231, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.06400000175926834}, {\"match_weight\": 7.15, \"match_probability\": 0.99302, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.0650000018067658}, {\"match_weight\": 7.24, \"match_probability\": 0.99341, \"prop\": 0.003000000026077032, \"cum_prop\": 0.06800000183284283}, {\"match_weight\": 7.32, \"match_probability\": 0.99379, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.06900000188034028}, {\"match_weight\": 7.39, \"match_probability\": 0.99408, \"prop\": 0.023000000044703484, \"cum_prop\": 0.09200000192504376}, {\"match_weight\": 7.5, \"match_probability\": 0.9945, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.09300000197254121}, {\"match_weight\": 7.52, \"match_probability\": 0.99457, \"prop\": 0.0020000000949949026, \"cum_prop\": 0.09500000206753612}, {\"match_weight\": 7.62, \"match_probability\": 0.99495, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.09600000211503357}, {\"match_weight\": 7.82, \"match_probability\": 0.99559, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.09700000216253102}, {\"match_weight\": 8.32, \"match_probability\": 0.99689, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.09800000221002847}, {\"match_weight\": 8.79, \"match_probability\": 0.99775, \"prop\": 0.020999999716877937, \"cum_prop\": 0.1190000019269064}, {\"match_weight\": 8.82, \"match_probability\": 0.99779, \"prop\": 0.0020000000949949026, \"cum_prop\": 0.12100000202190131}, {\"match_weight\": 8.9, \"match_probability\": 0.9979, \"prop\": 0.01899999938905239, \"cum_prop\": 0.1400000014109537}, {\"match_weight\": 8.96, \"match_probability\": 0.998, \"prop\": 0.014999999664723873, \"cum_prop\": 0.15500000107567757}, {\"match_weight\": 9.32, \"match_probability\": 0.99844, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.15600000112317502}, {\"match_weight\": 9.75, \"match_probability\": 0.99884, \"prop\": 0.006000000052154064, \"cum_prop\": 0.1620000011753291}, {\"match_weight\": 9.82, \"match_probability\": 0.99889, \"prop\": 0.006000000052154064, \"cum_prop\": 0.16800000122748315}, {\"match_weight\": 10.29, \"match_probability\": 0.9992, \"prop\": 0.01899999938905239, \"cum_prop\": 0.18700000061653554}, {\"match_weight\": 10.32, \"match_probability\": 0.99922, \"prop\": 0.0010000000474974513, \"cum_prop\": 0.188000000664033}, {\"match_weight\": 10.36, \"match_probability\": 0.99924, \"prop\": 0.019999999552965164, \"cum_prop\": 0.20800000021699816}, {\"match_weight\": 10.9, \"match_probability\": 0.99948, \"prop\": 0.00800000037997961, \"cum_prop\": 0.21600000059697777}, {\"match_weight\": 11.18, \"match_probability\": 0.99957, \"prop\": 0.004000000189989805, \"cum_prop\": 0.22000000078696758}, {\"match_weight\": 11.32, \"match_probability\": 0.99961, \"prop\": 0.003000000026077032, \"cum_prop\": 0.2230000008130446}, {\"match_weight\": 11.83, \"match_probability\": 0.99973, \"prop\": 0.003000000026077032, \"cum_prop\": 0.22600000083912164}, {\"match_weight\": 12.06, \"match_probability\": 0.99977, \"prop\": 0.0020000000949949026, \"cum_prop\": 0.22800000093411654}, {\"match_weight\": 12.23, \"match_probability\": 0.99979, \"prop\": 0.003000000026077032, \"cum_prop\": 0.23100000096019357}, {\"match_weight\": 12.47, \"match_probability\": 0.99982, \"prop\": 0.014999999664723873, \"cum_prop\": 0.24600000062491745}, {\"match_weight\": 12.52, \"match_probability\": 0.99983, \"prop\": 0.006000000052154064, \"cum_prop\": 0.2520000006770715}, {\"match_weight\": 12.64, \"match_probability\": 0.99984, \"prop\": 0.004000000189989805, \"cum_prop\": 0.2560000008670613}, {\"match_weight\": 12.75, \"match_probability\": 0.99985, \"prop\": 0.003000000026077032, \"cum_prop\": 0.25900000089313835}, {\"match_weight\": 12.9, \"match_probability\": 0.99987, \"prop\": 0.004000000189989805, \"cum_prop\": 0.26300000108312815}, {\"match_weight\": 13.34, \"match_probability\": 0.9999, \"prop\": 0.007000000216066837, \"cum_prop\": 0.270000001299195}, {\"match_weight\": 13.42, \"match_probability\": 0.99991, \"prop\": 0.008999999612569809, \"cum_prop\": 0.2790000009117648}, {\"match_weight\": 13.64, \"match_probability\": 0.99992, \"prop\": 0.01600000075995922, \"cum_prop\": 0.295000001671724}, {\"match_weight\": 13.9, \"match_probability\": 0.99993, \"prop\": 0.014999999664723873, \"cum_prop\": 0.3100000013364479}, {\"match_weight\": 14.14, \"match_probability\": 0.99994, \"prop\": 0.024000000208616257, \"cum_prop\": 0.33400000154506415}, {\"match_weight\": 14.41, \"match_probability\": 0.99995, \"prop\": 0.008999999612569809, \"cum_prop\": 0.34300000115763396}, {\"match_weight\": 14.8, \"match_probability\": 0.99996, \"prop\": 0.020999999716877937, \"cum_prop\": 0.3640000008745119}, {\"match_weight\": 15.23, \"match_probability\": 0.99997, \"prop\": 0.01899999938905239, \"cum_prop\": 0.3830000002635643}, {\"match_weight\": 15.73, \"match_probability\": 0.99998, \"prop\": 0.11800000071525574, \"cum_prop\": 0.50100000097882}, {\"match_weight\": 16.8, \"match_probability\": 0.99999, \"prop\": 0.13099999725818634, \"cum_prop\": 0.6319999982370064}]}}, {\"mode\": \"vega-lite\"});\n",
              "</script>"
            ],
            "text/plain": [
              "alt.LayerChart(...)"
            ]
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "import splink.comparison_library as cl\n",
        "from splink import DuckDBAPI, Linker, SettingsCreator, block_on, splink_datasets\n",
        "\n",
        "db_api = DuckDBAPI()\n",
        "\n",
        "df = splink_datasets.fake_1000\n",
        "\n",
        "settings = SettingsCreator(\n",
        "    link_type=\"dedupe_only\",\n",
        "    comparisons=[\n",
        "        cl.JaroWinklerAtThresholds(\"first_name\", [0.9, 0.7]),\n",
        "        cl.JaroAtThresholds(\"surname\", [0.9, 0.7]),\n",
        "        cl.DateOfBirthComparison(\n",
        "            \"dob\",\n",
        "            input_is_string=True,\n",
        "            datetime_metrics=[\"year\", \"month\"],\n",
        "            datetime_thresholds=[1, 1],\n",
        "        ),\n",
        "        cl.ExactMatch(\"city\").configure(term_frequency_adjustments=True),\n",
        "        cl.EmailComparison(\"email\"),\n",
        "    ],\n",
        "    blocking_rules_to_generate_predictions=[\n",
        "        block_on(\"first_name\"),\n",
        "        block_on(\"surname\"),\n",
        "    ],\n",
        ")\n",
        "\n",
        "linker = Linker(df, settings, db_api)\n",
        "linker.training.estimate_u_using_random_sampling(max_pairs=1e6)\n",
        "\n",
        "blocking_rule_for_training = block_on(\"first_name\", \"surname\")\n",
        "\n",
        "linker.training.estimate_parameters_using_expectation_maximisation(\n",
        "    blocking_rule_for_training\n",
        ")\n",
        "\n",
        "blocking_rule_for_training = block_on(\"dob\")\n",
        "linker.training.estimate_parameters_using_expectation_maximisation(\n",
        "    blocking_rule_for_training\n",
        ")\n",
        "\n",
        "chart = linker.evaluation.unlinkables_chart()\n",
        "chart\n"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "base",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.10.8"
    },
    "orig_nbformat": 4
  },
  "nbformat": 4,
  "nbformat_minor": 2
}
